<html>
    <head>
        <title>存储库</title>
    </head>
    <body>
        <script>
            //  存储库

                    /*
                          "使用类似集合的接口在领域层和数据映射层之间进行中介，一访问领域对象。"  -------《领域驱动设计》Marion Fowler 著

                          实际上，存储库用于对领域对象执行数据库操作。通常每个聚合根或实体都是用一个单独的存储库。
                    */

                    /*
                        一、通用存储库  

                                  ABP可以为每个聚合根或实体提供一个默认通用存储库。你可以将IRepository<TEntity, TKey>注入你的服务并执行标准的CRUD操作。

                                  应正确配置数据库提供程序层，以便能够使用默认的通用存储库。如果你已经使用启动模板创建了项目，则已经完成。

                                  如果没有，请参阅数据库提供程序文档（EF Core/MongoDB）对其进行配置。

                                  1、默认存储库用法：

                                                eg： 通用存储的用法

                                                                using System;
                                                                using System.Threading.Tasks;
                                                                using Volo.Abp.Application.Services;
                                                                using Volo.Abp.Domain.Repositories;

                                                                namespace Demo
                                                                {
                                                                    public class PersonAppService : ApplicationService
                                                                    {
                                                                        private readonly IRepository<Person, Guid> _personRepository;  // 注入通用存储

                                                                        public PersonAppService(IRepository<Person, Guid> personRepository)
                                                                        {
                                                                            _personRepository = personRepository;
                                                                        }

                                                                        public async Task CreateAsync(CreatePersonDto input)
                                                                        {
                                                                            var person = new Person(input.Name);

                                                                            await _personRepository.InsertAsync(person);
                                                                        }

                                                                        public async Task<int> GetCountAsync(string filter)
                                                                        {
                                                                            return await _personRepository.CountAsync(p => p.Name.Contains(filter));  // 使用通用存储
                                                                        }
                                                                    }
                                                                }

                                                        PersonAppService简单的在它的构造函数中注入IRepository<Person, Guid>。     
                                                        
                                                        CreateAsync方法使用InsertAsync来保存新实体。

                                                        GetCountAsync方法获取数据库中所有人员的过滤计数。


                                 2、默认存储库中的方法：

                                            通用存储库提供了一些开箱即用的标准CRUD功能：

                                                    ①、GetAsync：通过其Id或谓词（lambda表达式）返回单个实体。

                                                                    如果未找到请求的实体，则抛出EntityNotFoundException。

                                                                    如果存在多个具有给定谓词的实体，则抛出InvalidOperationException。

                                                    ②、FindAsync：通过其Id或谓词（lambda表达式）返回单个实体。

                                                                    如果未找到请求的实体，则返回null。

                                                                    如果存在多个具有给定谓词的实体，则抛出InvalidOperationException。

                                                    ③、InsertAsync：将新实体插入数据库。

                                                    ④、UpdateAsync：更新数据库中的现有实体。

                                                    ⑤、DeleteAsync：从数据库中删除给定的实体。

                                                                    此方法有一个重载，它采用谓词（lambda表达式）来删除多个实体以满足给定条件。

                                                                    如果要删除单个实体，请使用GetAsync或FindAsync获取它。
                                                        
                                                    ⑥、GetListAsync：返回数据库中所有实体的列表。

                                                    ⑦、GetPagedListAsync：返回有限的实体列表。获取skipCount、maxResultCount和sorting参数。

                                                    ⑧、GetCountAsync：获取数据库中所有实体的计数。

                                            这些方法有重载。

                                                    提供UpdateAsync和DeleteAsync方法以按实体对象或其ID更新或删除实体。

                                                    提供DeleteAsync方法以通过过滤器删除多个实体。



                                 3、通过存储库进行Linq查询

                                            存储库提供GetQueryableAsync()方法，该方法返回一个IQueryable<TEntity>对象。    

                                            你可以使用此对象对数据库中的实体执行LINQ查询。

                                                    ①、标准Linq
                                                            eg:  在存储库基础上使用Linq

                                                                    using System;
                                                                    using System.Linq;
                                                                    using System.Collections.Generic;
                                                                    using System.Threading.Tasks;
                                                                    using Volo.Abp.Application.Services;
                                                                    using Volo.Abp.Domain.Repositories;

                                                                    namespace Demo
                                                                    {
                                                                        public class PersonAppService : ApplicationService
                                                                        {
                                                                            private readonly IRepository<Person, Guid> _personRepository;

                                                                            public PersonAppService(IRepository<Person, Guid> personRepository)
                                                                            {
                                                                                _personRepository = personRepository;
                                                                            }

                                                                            public async Task<List<PersonDto>> GetListAsync(string filter)
                                                                            {
                                                                                // 获取 IQueryable<Person> 对象
                                                                                IQueryable<Person> queryable = await _personRepository.GetQueryableAsync();

                                                                                // 创建Linq查询
                                                                                var query = from person in queryable
                                                                                    where person.Name == filter
                                                                                    orderby person.Name
                                                                                    select person;

                                                                                // 执行Linq查询
                                                                                var people = query.ToList();

                                                                                // 映射为Dto
                                                                                return people.Select(p => new PersonDto {Name = p.Name}).ToList();
                                                                            }
                                                                        }
                                                                    }

                                                    ②、方法Linq

                                                            你还可以使用LINQ扩展方法：

                                                                    public async Task<List<PersonDto>> GetListAsync(string filter)
                                                                    {
                                                                        // 获取IQueryable<Person>对象
                                                                        IQueryable<Person> queryable = await _personRepository.GetQueryableAsync();

                                                                        // 执行方法Linq查询
                                                                        var people = queryable
                                                                            .Where(p => p.Name.Contains(filter))
                                                                            .OrderBy(p => p.Name)
                                                                            .ToList();

                                                                        // 映射为Dto返回
                                                                        return people.Select(p => new PersonDto {Name = p.Name}).ToList();
                                                                    }


                                 4、批量操作  
                                 
                                            有一些方法可以在数据库中执行批量操作：

                                                    ①、InsertManyAsync

                                                    ②、UpdateManyAsync

                                                    ③、DeleteManyAsync

                                            这些方法适用于多个实体，如果底层数据库提供程序支持，则可以利用批量操作。


                                                当你使用UpdateManyAsync和DeleteManyAsync方法时，可能无法进行乐观并发控制。



                                 5、软删除、硬删除

                                            DeleteAsync方法不会删除该实体。软删除实体在数据库中被标记为“已删除”。
                                            
                                            数据过滤器系统确保软删除的实体不会正常从数据库中检索。

                                            如果你的实体是软删除实体，你可以使用HardDeleteAsync方法从数据库中物理删除该实体以防需要。

                                            有关软删除的更多信息，请参阅"数据过滤文档"。



                                6、直接删除  Delete Direct

                                            DeleteDirectAsync方法删除所有适合给定谓词的实体。它直接从数据库中删除实体，而不获取它们。


                                            某些功能（如软删除、多租户和审计日志记录）可能会因为直接删除二不起作用，因此在需要时请谨慎使用此方法。

                                            如果你需要这些功能，请使用DeleteAsync方法。

                                            目前只有EF Core支持它，对于不支持直接删除的ORM，我们将回退到现有的DeleteAsync方法。



                                7、确保实体存在

                                            EnsureExistsAsync扩展方法接受实体id或实体查询表达式以确保实体存在，否则，它将抛出EntityNotFoundException。



                                
                                8、启用/禁用更改跟踪   Enabling / Disabling the Change Tracking

                                            ABP提供存储库扩展方法和属性，可用于控制底层数据库提供程序中查询实体的更改跟踪行为。

                                            如果你出于只读目的从数据库中查询许多实体，禁用更改跟踪可以提高性能。

                                            查询单个或几个实体不会产生太大的性能差异，但你可以随时自由使用它。


                                            注意：

                                                如果底层数据库提供程序不支持更改跟踪，那么这个系统将没有任何效果。
                                                
                                                Entity Framework Core支持更改跟踪，例如，而MongoDB提供程序不支持它。


                                            ①、用于更改跟踪的存储库扩展方法

                                                     除非你明确禁用它，否则将启用更改跟踪。

                                                        (1)、使用 DisableTracking 扩展方法
                                                                eg: 使用 DisableTracking 扩展方法

                                                                        public class MyDemoService : ApplicationService
                                                                        {
                                                                            private readonly IRepository<Person, Guid> _personRepository;

                                                                            public MyDemoService(IRepository<Person, Guid> personRepository)
                                                                            {
                                                                                _personRepository = personRepository;
                                                                            }

                                                                            public async Task DoItAsync()
                                                                            {
                                                                                // 在这里，实体更改追踪功能还是开启的。


                                                                                
                                                                                using (_personRepository.DisableTracking()) // 此时禁用实体更改追踪
                                                                                {
                                                                                    // 不再追踪实体更改
                                                                                    var list = await _personRepository.GetPagedListAsync(0, 100, "Name ASC");
                                                                                }
                                                                                
                                                                                // 此处，实体更改追踪又被重新启用。
                                                                            }
                                                                        }

                                                                    DisableTracking扩展方法返回一个IDisposable对象，因此一旦using块结束，你可以安全地将更改跟踪行为恢复到以前的状态。

                                                                    基本上，DisableTracking方法确保在using块内禁用更改跟踪，但不会影响using块之外。

                                                                    这意味着，如果更改跟踪已经禁用，DisableTracking和一次性返回值什么也不做。

                                                                    EnableTracking()方法的工作方式与DisableTracking()方法完全相反。
                                                                    
                                                                    你通常不会使用它（因为默认情况下已经启用了更改跟踪），但如果你需要，它就在那里。


                                                        
                                                        (2)、使用 "控制变更跟踪的特性"  [DisableEntityChangeTracking] 和 [EnableEntityChangeTracking ]

                                                                    eg:  在方法上使用 [DisableEntityChangeTracking]特性


                                                                                [DisableEntityChangeTracking]
                                                                                public virtual async Task<List<PersonDto>> GetListAsync()
                                                                                {
                                                                                    // 此处禁用实体更改追踪

                                                                                    var people = await _personRepository.GetListAsync();
                                                                                    return ObjectMapper.Map<List<Person>, List<PersonDto>(people);
                                                                                }

                                                                      EnableEntityChangeTracking可用于相反的目的，它确保为给定方法启用更改跟踪。

                                                                      由于更改跟踪默认启用，因此只有当你知道你的方法是从禁用更改跟踪的上下文中调用时，才可能需要EnableEntityChangeTracking。

                                                                      [DisableEntityChangeTracking] 和 [EnableEntityChangeTracking] 属性可以用于方法或类（这会影响所有类方法）。
                                                     
                                                     
                                                                      ABP使用动态代理来使这些属性工作。这里有一些规则：

                                                                                如果你没有通过接口注入服务（如IPersonAppService），则服务的方法必须是virtual。
                                                                                
                                                                                否则，动态代理/拦截系统无法工作。

                                                                                仅拦截async方法（返回Task或Task<T>的方法）。


                                            ★、更改跟踪行为不会影响从InsertAsync和UpdateAsync方法返回的跟踪实体对象。
                                            
                                                 从这些方法返回的对象始终被跟踪（如果底层提供程序具有更改跟踪功能），你对这些对象所做的任何更改都将保存到数据库中。

                    */




                    /*
                        二、其他通用存储库类型  Other Generic Repository Types


                                        标准IRepository<TEntity, TKey>接口公开了标准的IQueryable<TEntity>，你可以使用标准的LINQ方法自由查询。

                                        这对于大多数应用程序来说都很好。

                                        但是，某些ORM提供程序或数据库系统可能不支持标准的IQueryable接口。

                                        如果你想使用这样的提供程序，你不能依赖IQueryable。



                                1、基本存储库  Basic Repository

                                            ABP提供IBasicRepository<TEntity, TPrimaryKey>和IBasicRepository<TEntity>接口来支持此类场景。

                                            你可以扩展这些接口（并且可以选择从BasicRepositoryBase派生）来为你的实体创建自定义存储库。


                                            依赖于IBasicRepository但不依赖于IRepository的优点是可以使用所有数据源，即使它们不支持IQueryable。


                                            主要供应商，如Entity Framework、NHibernate或MongoDB已经支持IQueryable。

                                            因此，使用IRepository是建议的典型应用程序的方式。

                                            然而，可重用模块开发人员可能会考虑IBasicRepository来支持更广泛的数据源。

                                
                                2、只读存储库   Read Only Repositories 

                                            对于那些只想依赖存储库的查询功能的人来说，还有IReadOnlyRepository<TEntity, TKey>和IReadOnlyBasicRepository<Tentity, TKey>接口。

                                            (1)、   IReadOnlyRepository<TEntity, TKey>

                                                         IReadOnlyRepository<TEntity, TKey>派生IReadOnlyBasicRepository<Tentity, TKey>并提供以下属性和方法：

                                                            ①、属性：

                                                                    AsyncExecuter：用于异步执行IQueryable<T>对象，而不依赖于实际的数据库提供程序的服务。

                                                            ②、方法：

                                                                    GetListAsync()
                                                                    GetQueryableAsync()
                                                                    WithDetails() 1个重载
                                                                    WithDetailsAsync()1重载

                                            
                                            (2)、 IReadOnlyBasicRepository<Tentity, TKey>

                                                        而IReadOnlyBasicRepository<Tentity, TKey>提供以下方法：
                                                        
                                                                    GetCountAsync()
                                                                    GetListAsync()
                                                                    GetPagedListAsync()   



                                            ★、只读存储库在EF core 中的行为

                                                       Entity Framework Core只读存储库实现使用EF Core的无跟踪功能。     

                                                       这意味着EF Core更改跟踪器不会跟踪从存储库返回的实体，因为预计你不会更新从只读存储库查询的实体。

                                                       如果你需要跟踪实体，你仍然可以在LINQ表达式上使用AsTrack（）扩展方法，
                                                       
                                                       或在存储库对象上使用EnableTracking()扩展方法（请参阅本文档中的启用/禁用更改跟踪部分）。

                                                    仅当存储库对象被注入只读存储库接口之一（IReadOnlyRepository<...>或IReadOnlyBasicRepository<...>）时，此行为才有效。
                                                    
                                                    如果你注入了标准存储库（例如IRepository<...>），然后将其强制转换为只读存储库接口，则不起作用。
                                 
                                                    
                                3、没有主键的通用存储库

                                            如果你的实体没有Id主键（例如，它可能有一个复合主键），那么你不能使用上面定义的IRepository<TEntity, TKey>（或基本/只读版本）。
                                            
                                            在这种情况下，你可以为你的实体注入和使用IRepository<TEntity>。

                                               
                                              IRepository<TEntity>缺少一些通常与实体的Id属性一起使用的方法，因为在这种情况下实体没有Id属性，所以这些方法不可用。

                                              一个例子是Get方法，它获取一个id并返回具有给定id的实体。但是，你仍然可以使用IQueryable<TEntity>特性通过标准LINQ方法查询实体。
                    */

                    /*
                        三、自定义存储库  Custom Repositories

                                    对于大多数情况，默认的通用存储库就足够了。但是，你可能需要为你的实体创建一个自定义存储库类。

                                    但是，建议继承现有的存储库接口和类，以使你的工作更容易，并开箱即用的标准方法。


                                    1、声明自定义存储接口

                                               首先，在你的域层中定义一个接口：
                                               
                                                    public interface IPersonRepository : IRepository<Person, Guid>
                                                    {
                                                        Task<Person> FindByNameAsync(string name);
                                                    }

                                                接口扩展了IRepository<Person, Guid>以利用预构建的存储库功能。、


                                    2、实现自定义存储库

                                                自定义存储库与你正在使用的数据访问工具类型紧密耦合。

                                                在此示例中，我们将使用Entity Framework Core：

                                                                                    // 继承通用存储库基类                          实现自定义存储库接口
                                                    public class PersonRepository : EfCoreRepository<MyDbContext, Person, Guid>, IPersonRepository
                                                    {
                                                        public PersonRepository(IDbContextProvider<TestAppDbContext> dbContextProvider) 
                                                            : base(dbContextProvider)
                                                        {

                                                        }

                                                        public async Task<Person> FindByNameAsync(string name)
                                                        {
                                                            var dbContext = await GetDbContextAsync();
                                                            return await dbContext.Set<Person>()
                                                                .Where(p => p.Name == name)
                                                                .FirstOrDefaultAsync();
                                                        }
                                                    }


                                            你可以直接访问数据访问提供程序（在本例中为DbContext）来执行操作。

                                            有关自定义存储库的更多信息，请参阅EF Core文档。
                    */

                    /*
                        四、IQueryable<T>（可查询接口)的异步操作

                                    IRepository提供了GetQueryableAsync()来获得一个IQueryable，这意味着你可以直接在上面使用LINQ扩展方法，如上面“Quering/LINQ over the Repositories”部分的示例所示。

                                    eg： 使用Where(...)和ToList()扩展方法

                                            //  获取 IQueryable<Person>对象
                                            var queryable = await _personRepository.GetQueryableAsync();
                                            var people = queryable
                                                .Where(p => p.Name.Contains(nameFilter))
                                                .ToList();

                                    .ToList，Count()…是System.Linq命名空间中定义的标准扩展方法（请参下面地址文档）: 
                                    
                                            https://learn.microsoft.com/en-us/dotnet/api/system.linq.queryable?view=net-9.0

                                    相反，你通常希望使用.ToListAsync()，.CountAsync()…，能够编写真正的异步代码。

                                    但是，当你使用标准应用程序启动模板创建新项目时，你会看到你不能使用"应用程序层"或"领域层"中的所有异步扩展方法，因为：

                                            ①、这些异步方法不是标准的LINQ方法，是EF Core 对 IQueryable 的扩展方法，它们在Microsoft. EntityFrameworkCoreNuGet 包中定义。

                                            ②、标准模板没有从领域层和应用程序层引用到EF Core包，以独立于数据库提供程序。

                                    
                                    根据你的需求和开发模型，你有以下选项可以使用异步方法:



                                1、在领域层或应用程引用数据库提供程序员NuGet包

                                                最简单的解决方案是直接从你要使用这些异步方法的项目中添加EF Core包。

                                                将 "Volo. Abp.EntityFrameworkCore" NuGet包添加到你的项目中，该项目间接引用EF Core包。

                                                这可确保你使用与应用程序其余部分兼容的正确版本的EF Core。

                                                当你将NuGet包添加到你的项目时，你可以充分利用EF Core扩展方法。


                                                eg: 添加EF Core包后直接使用ToListAsync()

                                                        var queryable = await _personRepository.GetQueryableAsync();
                                                        var people = queryable
                                                            .Where(p => p.Name.Contains(nameFilter))
                                                            .ToListAsync();


                                                建议采用这种方法:

                                                        如果你正在开发一个应用程序并且你不打算在未来更改EF Core，或者如果你以后需要更改它，你可以容忍。
                                                        
                                                        我们相信如果你正在开发最终应用程序，这是合理的。


                                2、使用IRepository（默认存储库的扩展方法）异步扩展方法

                                                ABP为存储库提供异步扩展方法，类似于异步LINQ扩展方法。

                                                eg： 在存储库上使用CountAsync和FirstOrDefaultAsync方法

                                                    
                                                        var countAll = await _personRepository
                                                            .CountAsync();

                                                        var count = await _personRepository
                                                            .CountAsync(x => x.Name.StartsWith("A"));

                                                        var book1984 = await _bookRepository
                                                            .FirstOrDefaultAsync(x => x.Name == "John");  

                                             默认存储库对象上直接支持标准的Linq查询：

                                                        AllAsync，
                                                        AnyAsync，
                                                        AverageAsync，
                                                        ContainsAsync，
                                                        CountAsync，
                                                        FirstAsync，
                                                        FirstOrDefaultAsync，
                                                        LastAsync，
                                                        LastOrDefaultAsync，
                                                        LongCountAsync，
                                                        MaxAsync，
                                                        MinAsync，
                                                        SingleAsync，
                                                        SingleOrDefaultAsync，
                                                        SumAsync，
                                                        ToArrayAsync，
                                                        ToListAsync


                                            这种方法仍然有一个限制。它是基于默认存储库的功能，不能在IQueryable对象上使用。

                                            eg:  // 这个例子中的使用方法是不支持的：

                                                        var queryable = await _bookRepository.GetQueryableAsync();
                                                        var count = await queryable.Where(x => x.Name.Contains("A")).CountAsync(); // 不支持


                                                这是因为此示例中的CountAsync()方法是在IQueryable接口上调用的，而不是在存储库对象上。

                                                

                                        尽可能建议使用此方法



                               3、使用IAsyncQueryableExecuter

                                            IAsyncQueryableExecuter是一个服务，用于异步执行IQueryable<T>对象，而"不依赖于实际的数据库提供程序"。

                                            eg: 注入并使用IAsyncQueryableExecuter.ToListAsync()方法

                                                        using System;
                                                        using System.Collections.Generic;
                                                        using System.Linq;
                                                        using System.Threading.Tasks;
                                                        using Volo.Abp.Application.Dtos;
                                                        using Volo.Abp.Application.Services;
                                                        using Volo.Abp.Domain.Repositories;
                                                        using Volo.Abp.Linq;

                                                        namespace AbpDemo
                                                        {
                                                            public class ProductAppService : ApplicationService, IProductAppService
                                                            {
                                                                private readonly IRepository<Product, Guid> _productRepository;
                                                                private readonly IAsyncQueryableExecuter _asyncExecuter;  // 异步Queryable执行器服务注入


                                                                public ProductAppService(
                                                                    IRepository<Product, Guid> productRepository,
                                                                    IAsyncQueryableExecuter asyncExecuter)
                                                                {
                                                                    _productRepository = productRepository;
                                                                    _asyncExecuter = asyncExecuter;
                                                                }

                                                                public async Task<ListResultDto<ProductDto>> GetListAsync(string name)
                                                                {
                                                                    // 获取 IQueryable<T>
                                                                    var queryable = await _productRepository.GetQueryableAsync();
                                                                    
                                                                    // 创建查询
                                                                    var query = queryable
                                                                        .Where(p => p.Name.Contains(name))
                                                                        .OrderBy(p => p.Name);

                                                                    // 异步执行IQueryable<T>对象，并获取结果列表
                                                                    List<Product> products = await _asyncExecuter.ToListAsync(query);

                                                                    //...
                                                                }
                                                            }
                                                        }


                                            ApplicationService和DomainService基类已经预先注入了AsyncExecuter属性，无需显式构造函数注入即可使用。

                                            ABP使用实际数据库提供者的API异步执行查询。虽然这不是执行查询的常用方式，但它是在不依赖数据库提供者的情况下使用异步API的最佳方式。

                                            建议采用这种方法的原因：

                                                    如果你想在不依赖数据库提供程序的情况下开发应用程序代码。

                                                    如果你正在构建一个可重用库，它没有数据库提供程序集成包，但在某些情况下需要执行IQueryable<T>对象。

                               

                                4、自定义存储方法

                                          你始终可以创建自定义存储库方法并使用特定于数据库提供程序的API，例如此处的异步扩展方法。有关自定义存储库的更多信息，请参阅EF Core文档。
                                          
                                          
                                          建议采用这种方法的原因：

                                                  如果你想完全隔离你的领域层和应用程序层与数据库提供程序的话，这样很好。
                                                  
                                                  如果你开发了一个可重用的应用程序模块并且不想强制使用特定的数据库提供程序，这应该作为最佳实践来完成。

                    */
        </script>
    </body>
</html>